package com.hmdp.service.impl;

import cn.hutool.core.lang.UUID;
import com.hmdp.service.ILock;
import lombok.NoArgsConstructor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

import static com.hmdp.constants.RedisConstants.SECKILL_LOCK_KEY;


@NoArgsConstructor
public class SimpleRedisLock implements ILock {

    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;

    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("lua/unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }

    private String lockName;
    private StringRedisTemplate stringRedisTemplate;


    public SimpleRedisLock(String lockName, StringRedisTemplate stringRedisTemplate) {
        this.lockName = lockName;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 尝试获取锁
     *
     * @param timeOutSec 锁持有的超时时间 过期后自动释放（防止服务宕机，死锁）
     * @return boolean
     * @author Yhans
     * @date 2024/2/23 10:16
     */
    @Override
    public boolean tryLock(long timeOutSec) {
        //TODO 分布式情况下不同的服务可能有相同的线程id(jvm不同 具有不同的自增id 但可能相同)  所以采用uuid
        String threadId = ID_PREFIX + Thread.currentThread().getId();

        //(key,value)=>(seckill:lock:order: + userId,uuid+线程id)
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(SECKILL_LOCK_KEY + lockName, threadId, timeOutSec, TimeUnit.SECONDS);
        //拆箱空指针风险
        return Boolean.TRUE.equals(success);
    }

    /**
     * 释放锁
     */
/*    @Override
    public void unlock() {
        //线程标识
        String threadId = ID_PREFIX + Thread.currentThread().getId();

        //锁中的线程标识
        String redisThreadId = stringRedisTemplate.opsForValue().get(SECKILL_STOCK_KEY + lockName);

        //TODO 1判断标识是否一致 解决误删锁

        //TODO ?在判断之后 删除之前也可能阻塞(jvm垃圾回收的时候) 存在原子性问题
        if (threadId.equals(redisThreadId)) {
            stringRedisTemplate.delete(SECKILL_STOCK_KEY + lockName);
        }
    }*/

    /**
     * TODO 使用lua脚本释放锁
     */
    @Override
    public void unlock() {
        //调用脚本释放锁
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(SECKILL_LOCK_KEY + lockName),
                ID_PREFIX + Thread.currentThread().getId());
    }
}
